Skip to content

feat(assets-controller): add getAsset#9521

Open
Kriys94 wants to merge 1 commit into
mainfrom
feat/WPN-1473-assets-controller-get-asset
Open

feat(assets-controller): add getAsset#9521
Kriys94 wants to merge 1 commit into
mainfrom
feat/WPN-1473-assets-controller-get-asset

Conversation

@Kriys94

@Kriys94 Kriys94 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Explanation

WPN-1472 centralizes the Assets domain in @metamask/assets-controller so consumers (including Snaps) can read controller-owned balances, metadata, prices, and fiat values instead of tracking them independently. Today the controller exposes getAssets (bulk, per-account map), getAssetMetadata (metadata only), and getAssetsPrice, but there is no way to read a single combined asset for a specific account/asset pair.

This PR adds a getAsset read API to AssetsController:

  • getAsset(accountId, assetId): Asset | undefined returns the combined Asset (balance + metadata + price + computed fiatValue) for a single account/asset pair from current controller state, or undefined when a complete renderable asset is not available.
  • Exposed as the AssetsController:getAsset messenger action so it can be called through the messenger (e.g. by preinstalled Snaps once WPC-1017 lands).

Behavior details:

  • Reuses the same state-composition and filtering logic as getAssets (via the private #getAssetsFromState) so the returned shape never drifts: balance and metadata are required; a missing price falls back to { price: 0, lastUpdated: 0 } with fiatValue: 0; hidden/filtered assets resolve to undefined.
  • Reads from current state only — it does not trigger a data-source refresh.
  • Validates inputs and throws (rather than silently returning undefined) on an empty accountId or a malformed CAIP-19 assetId, so consumers can distinguish bad requests from absent data.
  • Normalizes the assetId (checksums EVM ERC-20 addresses) before lookup, consistent with how state is keyed.
  • #getAssetsFromState's accounts parameter was widened to Pick<InternalAccount, 'id'>[] (it only reads account.id) so getAsset can reuse it without constructing a full account object.

The generated messenger action types (AssetsControllerGetAssetAction) were regenerated and exported from index.ts, and a ### Added changelog entry was added.

References

  • Jira: WPN-1473 — [AssetsController] Add getAsset read API for Snap consumers
  • Part of the Assets migration epic (WPN-1472); sibling controller-side stories are WPN-1489 (stage-gated ingestion) and WPN-1490 (stage-gated ignoring of Snap events)
  • Consumer-side exposure to Snaps is blocked by WPC-1017 (messenger API exposure); the controller-side method in this PR is independent of that work

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Low Risk
Additive read-only API with tests and aligned with existing getAssets composition; no fetch or state-write behavior changes beyond the new method signature tweak on #getAssetsFromState.

Overview
Adds getAsset(accountId, assetId) and the AssetsController:getAsset messenger action so consumers can read one fully composed Asset (balance, metadata, price, fiatValue) from current state without calling bulk getAssets.

Implementation routes through #getAssetsFromState with the same filtering as getAssets (requires balance + metadata, zero price fallback, hidden assets excluded), normalizes CAIP-19 IDs, validates inputs (throws on empty account or invalid asset id), and does not trigger fetches. #getAssetsFromState now accepts Pick<InternalAccount, 'id'>[] so single-asset lookups do not need full account objects.

Exports AssetsControllerGetAssetAction, registers/unregisters the messenger handler, documents the API in generated action types, and adds changelog + unit tests.

Reviewed by Cursor Bugbot for commit 2ccbbe5. Bugbot is set up for automated code reviews on this repo. Configure here.

@Kriys94 Kriys94 force-pushed the feat/WPN-1473-assets-controller-get-asset branch 3 times, most recently from 8bdabbe to 6aa280d Compare July 15, 2026 14:55
@Kriys94 Kriys94 marked this pull request as ready for review July 15, 2026 14:58
@Kriys94 Kriys94 requested review from a team as code owners July 15, 2026 14:58
@Kriys94 Kriys94 temporarily deployed to default-branch July 15, 2026 14:59 — with GitHub Actions Inactive

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6aa280d. Configure here.

[{ id: accountId }],
[chainId],
['fungible'],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skips enabled-chain filtering

Medium Severity

getAsset passes only the asset’s own chain into #getAssetsFromState, while default getAssets filters with #enabledChains. A balance on a disabled network can be returned by getAsset but omitted from getAssets, breaking the documented parity between the two APIs.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6aa280d. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well this function will be used by Snaps independently from the enabledChains

Co-authored-by: Cursor <cursoragent@cursor.com>
@Kriys94 Kriys94 force-pushed the feat/WPN-1473-assets-controller-get-asset branch from 6aa280d to 2ccbbe5 Compare July 15, 2026 15:02
Comment on lines +1815 to +1819
const assets = this.#getAssetsFromState(
[{ id: accountId }],
[chainId],
['fungible'],
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - A tad nuts that we are calculating all assets, only to return a single asset here.

This is okay for now, we can raise/improve moving forward.


#getAssetsFromState(
accounts: InternalAccount[],
accounts: Pick<InternalAccount, 'id'>[],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good! Keep interface minimal!
Easier to test and use.

});
});

describe('getAsset', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌶️ this test file is too big, impossible to read!

Thoughts on:

  1. Making this spec more dry. I think we can leverage test tables (it.each)
  2. spicer -- separate test for each method, truly isolated, easy to read 😈

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine keeping this as is... but I'd like to pencil in test cleanup.

@Prithpal-Sooriya Prithpal-Sooriya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving with comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants